package org.smartboot.flow.core.parser;

import org.smartboot.flow.core.Adapter;
import org.smartboot.flow.core.Condition;
import org.smartboot.flow.core.FlowEngine;
import org.smartboot.flow.core.Pipeline;
import org.smartboot.flow.core.builder.AdapterBuilder;
import org.smartboot.flow.core.builder.ChooseBuilder;
import org.smartboot.flow.core.builder.EngineBuilder;
import org.smartboot.flow.core.builder.ExecutableBuilder;
import org.smartboot.flow.core.builder.IfComponentBuilder;
import org.smartboot.flow.core.builder.PipelineBuilder;
import org.smartboot.flow.core.component.AdapterComponent;
import org.smartboot.flow.core.component.ChooseComponent;
import org.smartboot.flow.core.component.Component;
import org.smartboot.flow.core.component.PipelineComponent;
import org.smartboot.flow.core.executable.Executable;
import org.smartboot.flow.core.parser.definition.AdapterDefinition;
import org.smartboot.flow.core.parser.definition.ChooseDefinition;
import org.smartboot.flow.core.parser.definition.ElementDefinition;
import org.smartboot.flow.core.parser.definition.EngineDefinition;
import org.smartboot.flow.core.parser.definition.IfElementDefinition;
import org.smartboot.flow.core.parser.definition.PipelineComponentDefinition;
import org.smartboot.flow.core.parser.definition.PipelineDefinition;
import org.smartboot.flow.core.parser.definition.ScriptDefinition;
import org.smartboot.flow.core.script.ScriptCondition;
import org.smartboot.flow.core.util.AssertUtil;
import org.smartboot.flow.core.util.AuxiliaryUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author qinluo
 * @date 2022-11-15 20:17:18
 * @since 1.0.0
 */
@SuppressWarnings({"rawtypes", "unchecked"})
public class BuilderDefinitionVisitor implements DefinitionVisitor {

    private final Map<String, Component<?, ?>> namedComponents = new ConcurrentHashMap<>();
    private final Map<String, Pipeline> assembledPipelines = new ConcurrentHashMap<>();
    private final Map<String, PipelineBuilder<?, ?>> namedPipelines = new ConcurrentHashMap<>();
    private final Map<String, EngineBuilder<?, ?>> namedEngines = new ConcurrentHashMap<>();
    private final Map<String, List<PipelineEndCallBack>> callbacks = new ConcurrentHashMap<>();
    private final Map<String, ScriptCondition> scriptConditions = new ConcurrentHashMap<>();
    private final ObjectCreator objectCreator;
    private final boolean useCache;

    public BuilderDefinitionVisitor(boolean useCache, ObjectCreator objectCreator) {
        this.useCache = useCache;
        this.objectCreator = objectCreator;
    }

    @Override
    public void visit(ElementDefinition ed) {
        if (ed instanceof ScriptDefinition) {
            ScriptDefinition sed = (ScriptDefinition) ed;
            ScriptCondition condition = objectCreator.create(sed.getType(), true);
            condition.setName(sed.getName());
            condition.setScript(sed.getScript());
            scriptConditions.put(ed.getIdentifier(), condition);
        } else {
            ed.visit(this);
        }

    }

    @Override
    public void visit(PipelineDefinition ed) {
        PipelineBuilder pipelineBuilder = namedPipelines.get(ed.getIdentifier());
        if (pipelineBuilder == null) {
            pipelineBuilder = new PipelineBuilder<>(ed.getIdentifier());
        }
        namedPipelines.put(ed.getIdentifier(), pipelineBuilder);
        List<ElementDefinition> children = ed.getChildren();
        for (ElementDefinition child : children) {
            child.visit(this);
        }

        for (ElementDefinition child : children) {
            pipelineBuilder.next(this.namedComponents.get(child.getIdentifier()));
        }

        Pipeline<?, ?> build = pipelineBuilder.build();
        invoke(ed.getIdentifier(), build);
        this.assembledPipelines.put(ed.getIdentifier(), build);
    }

    @Override
    public void visit(EngineDefinition ed) {
        String engineName = ed.getIdentifier();
        EngineBuilder<?, ?> engineBuilder = new EngineBuilder<>(engineName);

        PipelineBuilder pipelineBuilder = this.namedPipelines.get(ed.getPipeline());
        if (pipelineBuilder == null) {
            pipelineBuilder = new PipelineBuilder<>(ed.getPipeline());
            this.namedPipelines.put(ed.getPipeline(), pipelineBuilder);
            this.collect(ed.getPipeline(), (engineBuilder::pipeline));
        }  else if (assembledPipelines.get(ed.getPipeline()) != null) {
            engineBuilder.pipeline(assembledPipelines.get(ed.getPipeline()));
        } else {
            this.collect(ed.getPipeline(), (engineBuilder::pipeline));
        }

        this.namedEngines.put(engineName, engineBuilder);
    }

    @Override
    public void visit(PipelineComponentDefinition ed) {
        String engineName = ed.getIdentifier();
        PipelineBuilder<?,?> pipelineBuilder = this.namedPipelines.get(ed.getPipeline());

        PipelineComponent pipelineComponent = new PipelineComponent<>();
        if (pipelineBuilder == null) {
            pipelineBuilder = new PipelineBuilder<>(ed.getPipeline());
            this.namedPipelines.put(ed.getPipeline(), pipelineBuilder);
            this.collect(ed.getPipeline(), pipelineComponent::setPipeline);
        } else if (assembledPipelines.get(ed.getPipeline()) != null) {
            pipelineComponent.setPipeline(assembledPipelines.get(ed.getPipeline()));
        } else {
            this.collect(ed.getPipeline(), (pipelineComponent::setPipeline));
        }

        this.namedComponents.put(engineName, pipelineComponent);
    }

    private void collect(String name, PipelineEndCallBack callBack) {
        List<PipelineEndCallBack> pipelineEndCallBacks = this.callbacks.get(name);
        if (pipelineEndCallBacks == null) {
            pipelineEndCallBacks = new ArrayList<>();
        }
        pipelineEndCallBacks.add(callBack);
        this.callbacks.put(name, pipelineEndCallBacks);
    }

    private void invoke(String name, Pipeline<?, ?> pipeline) {
        List<PipelineEndCallBack> cbs = this.callbacks.getOrDefault(name, new ArrayList<>());
        cbs.forEach(p -> p.execute(pipeline));
    }

    @Override
    public void visitBasic(ElementDefinition ed) {
        ExecutableBuilder<?, ?> builder = new ExecutableBuilder<>();
        ed.getAttributes().forEach(p -> {
            // resolve value type.
            builder.apply(p.getAttribute(), p.getValue());
        });
        Component<?, ?> component = builder.newAdapter((Executable) newInstance(ed.getExecute()));
        namedComponents.put(ed.getIdentifier(), component);
    }

    private Object newInstance(String type) {
        try {
            return objectCreator.create(type, useCache);
        } catch (Exception e) {
            throw new IllegalStateException(type, e);
        }
    }

    @Override
    public void visit(IfElementDefinition ed) {
        String test = ed.getTest();
        Condition condition = null;
        if (AuxiliaryUtils.isType(test)) {
            condition = (Condition) newInstance(test);
        } else if (ed.getContext().getRegistered(test) != null) {
            condition = getInternalObject(test, ed.getContext());
        }

        AssertUtil.notNull(condition, "can't find condition for if-element, test = " + test);
        IfComponentBuilder<?,?> ifComponentBuilder = new IfComponentBuilder<>(condition);
        ed.getAttributes().forEach(p -> {
            // resolve value type.
            ifComponentBuilder.apply(p.getAttribute(), p.getValue());
        });

        ed.getIfThenRef().visit(this);
        if (ed.getIfElseRef() != null) {
            ed.getIfElseRef().visit(this);
        }

        Component then = this.namedComponents.get(ed.getIfThenRef().getIdentifier());
        Component elseBranch = null;
        if (ed.getIfElseRef() != null) {
            elseBranch = this.namedComponents.get(ed.getIfElseRef().getIdentifier());
        }
        Component<?, ?> build = ifComponentBuilder.build(then, elseBranch);
        this.namedComponents.put(ed.getIdentifier(), build);
    }

    public <T> T getInternalObject(String test, ParserContext context) {
        ElementDefinition registered = context.getRegistered(test);
        AssertUtil.notNull(registered, "registered condition def[" + test + "] not found");
        this.visit(registered);
        return (T)scriptConditions.get(test);
    }

    @Override
    public void visit(ChooseDefinition ed) {
        String test = ed.getTest();
        Condition condition = null;
        if (AuxiliaryUtils.isType(test)) {
            condition = (Condition) newInstance(test);
        } else if (ed.getContext().getRegistered(test) != null) {
            condition = getInternalObject(test, ed.getContext());
        }

        AssertUtil.notNull(condition, "can't find condition for choose-element, test = " + test);
        ChooseBuilder chooseBuilder = new ChooseBuilder<>(condition);
        ed.getAttributes().forEach(p -> {
            // resolve value type.
            chooseBuilder.apply(p.getAttribute(), p.getValue());
        });

        List<ElementDefinition> chooseCaseList = ed.getChooseCaseList();
        ElementDefinition chooseDefaultRef = ed.getChooseDefaultRef();

        for (ElementDefinition definition : chooseCaseList) {
            definition.visit(this);
        }

        if (chooseDefaultRef != null) {
            chooseDefaultRef.visit(this);
        }

        for (ElementDefinition definition : chooseCaseList) {
            chooseBuilder.newBranch(definition.getWhen(), this.namedComponents.get(definition.getIdentifier()));
        }

        Component<?, ?> defaultBranch = null;
        if (chooseDefaultRef != null) {
            defaultBranch = this.namedComponents.get(chooseDefaultRef.getIdentifier());

        }
        ChooseComponent<?, ?> build = chooseBuilder.build(defaultBranch);
        build.setAllBranchWasString(true);
        this.namedComponents.put(ed.getIdentifier(), build);
    }

    @Override
    public void visit(AdapterDefinition ed) {
        String execute = ed.getExecute();
        Object adapter;
        if (AuxiliaryUtils.isType(execute)) {
            adapter = newInstance(execute);
        } else {
            adapter = ed.getContext().getRegistered(execute);
        }
        AssertUtil.notNull(adapter, "can't find adapter , execute=" + execute);

        ed.getPipelineElement().visit(this);
        Component<?, ?> component = namedComponents.get(ed.getPipelineElement().getIdentifier());

        AdapterBuilder adapterBuilder = new AdapterBuilder((Adapter) adapter, component);
        ed.getAttributes().forEach(p -> {
            // resolve value type.
            adapterBuilder.apply(p.getAttribute(), p.getValue());
        });

        AdapterComponent adapterComponent = adapterBuilder.build();
        this.namedComponents.put(ed.getIdentifier(), adapterComponent);
    }

    /**
     * Return all parsed engine names.
     *
     * @return engine names.
     */
    public List<String> getEngineNames() {
        return new ArrayList<>(namedEngines.keySet());
    }

    @SuppressWarnings("unchecked")
    public <T, S> FlowEngine<T, S> getEngine(String name) {
        EngineBuilder<?, ?> engineBuilder = namedEngines.get(name);
        return (FlowEngine<T, S>)engineBuilder.build();
    }
}
